/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "cert_manager_api.h"

#include "cm_ipc_client.h"
#include "cm_log.h"
#include "cm_mem.h"
#include "cm_type.h"
#include "net_manager_api.h"

CM_API_EXPORT int32_t CmGetUserCertInfo(const struct CmBlob *certUri, uint32_t store, struct CertInfo *certificateInfo)
{
    CM_LOG_D("enter get cert info");
    if ((certUri == NULL) || (certificateInfo == NULL)) {
        return CMR_ERROR_NULL_POINTER;
    }

    int32_t ret = CmClientGetUserCertInfo(certUri, store, certificateInfo);
    CM_LOG_D("leave get cert info, result = %d", ret);
    return ret;
}

CM_API_EXPORT int32_t CmGetAppCert(const struct CmBlob *keyUri, const uint32_t store, struct Credential *certificate)
{
    CM_LOG_D("enter get app certificate");
    if (keyUri == NULL || certificate == NULL ||
        (store != CM_CREDENTIAL_STORE && store != CM_PRI_CREDENTIAL_STORE && store != CM_SYS_CREDENTIAL_STORE)) {
        return CMR_ERROR_INVALID_ARGUMENT;
    }

    int32_t ret = CmClientGetAppCert(keyUri, store, certificate);
    CM_LOG_D("leave get app certificate, result = %d", ret);
    return ret;
}

CM_API_EXPORT int32_t CmInit(const struct CmBlob *authUri, const struct CmSignatureSpec *spec, struct CmBlob *handle)
{
    CM_LOG_D("enter cert manager init");
    if ((authUri == NULL) || (spec == NULL) || (handle == NULL)) {
        CM_LOG_E("invalid input arguments");
        return CMR_ERROR_INVALID_ARGUMENT;
    }

    int32_t ret = CmClientInit(authUri, spec, handle);
    CM_LOG_D("leave cert manager init, result = %d", ret);
    return ret;
}

CM_API_EXPORT int32_t CmUpdate(const struct CmBlob *handle, const struct CmBlob *inData)
{
    CM_LOG_D("enter cert manager update");
    if ((handle == NULL) || (inData == NULL)) {
        CM_LOG_E("invalid input arguments");
        return CMR_ERROR_INVALID_ARGUMENT;
    }

    int32_t ret = CmClientUpdate(handle, inData);
    CM_LOG_D("leave cert manager update, result = %d", ret);
    return ret;
}

CM_API_EXPORT int32_t CmFinish(const struct CmBlob *handle, const struct CmBlob *inData, struct CmBlob *outData)
{
    CM_LOG_D("enter cert manager finish");
    if ((handle == NULL) || (inData == NULL) || (outData == NULL)) {
        CM_LOG_E("invalid input arguments");
        return CMR_ERROR_INVALID_ARGUMENT;
    }

    int32_t ret = CmClientFinish(handle, inData, outData);
    CM_LOG_D("leave cert manager finish, result = %d", ret);
    return ret;
}

CM_API_EXPORT int32_t CmAbort(const struct CmBlob *handle)
{
    CM_LOG_D("enter cert manager abort");
    if (handle == NULL) {
        CM_LOG_E("invalid input arguments");
        return CMR_ERROR_INVALID_ARGUMENT;
    }

    int32_t ret = CmClientAbort(handle);
    CM_LOG_D("leave cert manager abort, result = %d", ret);
    return ret;
}

static void log_cert(struct CertInfo *certInfo)
{
    if (certInfo) {
        DBG2(DBG_CFG, "CertInfo:");
        DBG2(DBG_CFG, "\t uri: %s", certInfo->uri);
        DBG2(DBG_CFG, "\t certAlias: %s", certInfo->certAlias);
        DBG2(DBG_CFG, "\t status: %d", certInfo->status);
        DBG2(DBG_CFG, "\t issuerName: %s", certInfo->issuerName);
        DBG2(DBG_CFG, "\t subjectName: %s", certInfo->subjectName);
        DBG2(DBG_CFG, "\t serial: %s", certInfo->serial);
        DBG2(DBG_CFG, "\t notBefore: %s", certInfo->notBefore);
        DBG2(DBG_CFG, "\t notAfter: %s", certInfo->notAfter);
        DBG2(DBG_CFG, "\t fingerprintSha256: %s", certInfo->fingerprintSha256);
        DBG2(DBG_CFG, "\t certInfo.size: %d", certInfo->certInfo.size);
        DBG2(DBG_CFG, "\t certInfo.data: %s", certInfo->certInfo.data);
    }
}

static void log_credential(struct Credential *certInfo)
{
    if (certInfo) {
        DBG2(DBG_CFG, "Certificate:");
        DBG2(DBG_CFG, "\t type: %s", certInfo->type);
        DBG2(DBG_CFG, "\t alias: %s", certInfo->alias);
        DBG2(DBG_CFG, "\t keyUri: %s", certInfo->keyUri);
        DBG2(DBG_CFG, "\t isExist: %d", certInfo->isExist);
        DBG2(DBG_CFG, "\t certNum: %d", certInfo->certNum);
        DBG2(DBG_CFG, "\t keyNum: %d", certInfo->keyNum);
        DBG2(DBG_CFG, "\t credData.size: %d", certInfo->credData.size);
        DBG2(DBG_CFG, "\t credData.data: %s", certInfo->credData.data);
    }
}

static chunk_t load_ca_cert(const uint8_t *uri_buf, uint32_t size)
{
    if (!uri_buf) {
        return chunk_empty;
    }

    struct CmBlob uri = { size, (uint8_t *)uri_buf };
    struct CertInfo caCert;
    unsigned int len = sizeof(struct CertInfo);
    (void)memset_s(&caCert, len, 0, len);
    caCert.certInfo.data = (uint8_t *)malloc(MAX_LEN_CERTIFICATE);
    if (caCert.certInfo.data == NULL) {
        return chunk_empty;
    }
    caCert.certInfo.size = MAX_LEN_CERTIFICATE;
    int32_t ret = CmGetUserCertInfo(&uri, CM_USER_TRUSTED_STORE, &caCert);
    if (ret != CM_SUCCESS) {
        return chunk_empty;
    }
    log_cert(&caCert);
    return chunk_create(caCert.certInfo.data, caCert.certInfo.size);
}

static chunk_t load_user_cert(const uint8_t *uri_buf, uint32_t size)
{
    if (!uri_buf) {
        return chunk_empty;
    }

    struct CmBlob uri = { size, (uint8_t *)uri_buf };
    struct Credential userCert;
    unsigned int len = sizeof(struct Credential);
    (void)memset_s(&userCert, len, 0, len);
    userCert.credData.data = (uint8_t *)malloc(MAX_LEN_CERTIFICATE_CHAIN);
    if (userCert.credData.data == NULL) {
        return chunk_empty;
    }
    userCert.credData.size = MAX_LEN_CERTIFICATE_CHAIN;
    int32_t ret = CmGetAppCert(&uri, CM_SYS_CREDENTIAL_STORE, &userCert);
    if (ret != CM_SUCCESS) {
        return chunk_empty;
    }
    log_credential(&userCert);
    return chunk_create(userCert.credData.data, userCert.credData.size);
}

CM_API_EXPORT chunk_t load_vpn_cert(enum IpsecVpnCertType type)
{
    uint8_t uri_buf[MAX_LEN_URI];
    memset_s(uri_buf, MAX_LEN_URI, 0, MAX_LEN_URI);
    uint32_t size = LoadVpnCertUri(type, uri_buf, MAX_LEN_URI);
    if (size < 1) {
        return chunk_empty;
    }
    if (type == CA_CERT) {
        return load_ca_cert(uri_buf, MAX_LEN_URI);
    }
    if (type == USER_CERT) {
        return load_user_cert(uri_buf, MAX_LEN_URI);
    }
    return chunk_empty;
}

CM_API_EXPORT int32_t cm_sign(const uint8_t *uri_buf, uint32_t size, chunk_t msg, chunk_t *authData,
    struct CmSignatureSpec *signSpec)
{
    if (!uri_buf || !authData || !signSpec) {
        DBG2(DBG_CFG, "cm_sign invalid params");
        return CM_FAILURE;
    }

    DBG2(DBG_CFG, "cm_sign start");
    struct CmBlob authUri = { size, (uint8_t *)uri_buf };
    uint64_t signHandleValue = 0;
    struct CmBlob signHandle = { sizeof(uint64_t), (uint8_t *)&signHandleValue };
    struct CmBlob message = { msg.len, msg.ptr };
    uint8_t signData[DEFAULT_SIGNATURE_LEN];
    (void)memset_s(signData, DEFAULT_SIGNATURE_LEN, 0, DEFAULT_SIGNATURE_LEN);
    struct CmBlob signature = { DEFAULT_SIGNATURE_LEN, (uint8_t *)signData };

    int32_t ret = CmInit(&authUri, signSpec, &signHandle);
    if (ret != CM_SUCCESS) {
        DBG2(DBG_CFG, " cm_sign, CmInit failed, ret = %d ", ret);
        return CM_FAILURE;
    }

    ret = CmUpdate(&signHandle, &message);
    if (ret != CM_SUCCESS) {
        DBG2(DBG_CFG, " cm_sign, CmUpdate failed, ret = %d ", ret);
        return CM_FAILURE;
    }

    struct CmBlob inData = { 0, NULL };
    ret = CmFinish(&signHandle, &inData, &signature);
    if (ret != CM_SUCCESS) {
        DBG2(DBG_CFG, " cm_sign, CmFinish failed, ret = %d ", ret);
        return CM_FAILURE;
    }
    *authData = chunk_alloc(signature.size);
    authData->len = signature.size;
    memcpy_s(authData->ptr, authData->len, signature.data, signature.size);
    return CM_SUCCESS;
}
